﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.DynamicInvoker.Implementation
{
    [Implements(typeof(IMemberInvoker),LifecycleType.Singleton)]
    public class MemberInvoker : IMemberInvoker,IInitialize
    {
        private IServiceContainer _serviceContainer;

        private readonly static Dictionary<int,MethodHandler> _methodHandlers 
            = new Dictionary<int, MethodHandler>();

        private readonly static IDictionary<string, MethodHandler> _fieldHandlers 
            = new Dictionary<string, MethodHandler>();

        private readonly static IDictionary<int,MethodInfo> _constructedGenericMethods 
            = new Dictionary<int, MethodInfo>();

        private static readonly object lockObject = new object();
        
        #region IMemberInvoker Members

        public object Invoke(object target, MemberInfo memberInfo, object[] arguments)
        {
            MethodHandler methodHandler;

            lock (lockObject)
            {
                if (arguments == null)
                    arguments = new object[]{};
                methodHandler = CreateDelegate(memberInfo, arguments);
            }

            //return methodHandler(target, arguments);            
            return methodHandler(target, arguments);            
        }

        public object Invoke(MemberInfo memberInfo, object[] arguments)
        {
            return Invoke(null, memberInfo, arguments);
        }

        #endregion

        
        public MethodHandler CreateDelegate(MemberInfo memberInfo, object[] arguments)
        {
            if (_methodHandlers.ContainsKey(memberInfo.GetHashCode()))             
                 return _methodHandlers[memberInfo.GetHashCode()];
            
            if (arguments == null)
                arguments = new object[] { };

            var targetMember = memberInfo;

            //Check to see if the target member is a field
            if (memberInfo.MemberType == MemberTypes.Field)
            {
                return HandleFieldMember((FieldInfo)targetMember, arguments);
            }


           

            //Check to see if the target member is a property.
            if (memberInfo.MemberType == MemberTypes.Property)
            {
                if (arguments.Length == 0)
                    targetMember = ((PropertyInfo)memberInfo).GetGetMethod(true);
                else                
                    targetMember = ((PropertyInfo) memberInfo).GetSetMethod(true);                    
                
            }

            //Check to see if the member is a generic method definition
            var method = targetMember as MethodInfo;

            if (method != null)
            {
                if (method.IsGenericMethodDefinition)
                {
                    var parameterTypes = _serviceContainer.GetService<IParameterTypeResolver>().GetTypes(arguments);
                    var hashCode = _serviceContainer.GetService<IHashBuilder>().CreateHashCode(memberInfo,
                                                                                            parameterTypes);
                    if (!_constructedGenericMethods.ContainsKey(hashCode))
                    {
                        targetMember = _serviceContainer.GetService<IMethodConstructor>()
                            .MakeConstructedMethods(new[] { method }, parameterTypes).FirstOrDefault();
                        _constructedGenericMethods.Add(hashCode, (MethodInfo)targetMember);
                    }
                    targetMember = _constructedGenericMethods[hashCode];
                }
            }

            
            if (!_methodHandlers.ContainsKey(targetMember.GetHashCode()))
            {
                //Get a IDynamicFactory service instance to create the method handler.                                
                var genericFactoryType =
                    typeof(IDynamicFactory<>).MakeGenericType(new[] { targetMember.GetType().BaseType });
                ;
                var factoryInstance = _serviceContainer.GetService(genericFactoryType);
                var methodHandler =
                    (MethodHandler)
                    factoryInstance.GetType().GetMethod("CreateDelegate").Invoke(factoryInstance,
                                                                                 new object[] { targetMember });
                _methodHandlers.Add(targetMember.GetHashCode(), methodHandler);
            }

            return _methodHandlers[targetMember.GetHashCode()];

        }



        private MethodHandler HandleFieldMember(FieldInfo fieldInfo, object[] arguments)
        {
            string key;
            IDynamicFactory<FieldInfo> factory;
            if (arguments.Length == 0)
            {
                //If no arguments are supplied, we assume that we want to get the field value
                key = string.Format("{0}_Get_{1}", fieldInfo.DeclaringType.Name, fieldInfo.Name);
                if (!_fieldHandlers.ContainsKey(key))
                {
                    factory = _serviceContainer.GetService<IDynamicFactory<FieldInfo>>("FieldGetterFactory");
                    _fieldHandlers.Add(key, factory.CreateDelegate(fieldInfo));
                }
            }
            else
            {
                key = string.Format("{0}_Set_{1}", fieldInfo.DeclaringType.Name, fieldInfo.Name);
                if (!_fieldHandlers.ContainsKey(key))
                {
                    factory = _serviceContainer.GetService<IDynamicFactory<FieldInfo>>("FieldSetterFactory");
                    _fieldHandlers.Add(key, factory.CreateDelegate(fieldInfo));
                }
            }

            return _fieldHandlers[key];
        }


        #region IInitialize<IServiceContainer> Members

        public void Initialize(IServiceContainer source)
        {
            _serviceContainer = source;
        }

        #endregion
    }
}
